#include <QtCore/qmath.h>
#include <QtCore/QtEndian>
#include <QtCore/QDateTime>
#include <QtSql/QSqlRecord>
#include "kernel/kernel.h"
#include "kernel/kernel_p.h"

namespace smsbilling {

static Kernel *kernel_instance = 0;

KernelPrivate::KernelPrivate() :
  QObject(0),
  server_id(-1),
  host_port(0),
  sync_db_time(5000),
  sync_db_timer(-1),
  log_level(smslog::WARNING),
  db_port(-1),
  num_connect_db(-1),
  exec_query_timeout(-1),
  billing_server(0),
  db_manager(0),
  thread(new QThread(this)),
  state(STOPED) {
  moveToThread(thread);

  connect(thread, SIGNAL(started()), this, SLOT(threadStarted()));

  connect(this, SIGNAL(sigStop()), this, SLOT(stop()));
}

KernelPrivate::~KernelPrivate() {
  if(billing_server) {
    delete billing_server;
    billing_server = 0;
  }
  if(db_manager) {
    delete db_manager;
    db_manager = 0;
  }

  kernel_instance = 0;
}

void KernelPrivate::timerEvent(QTimerEvent *e) {
  if(e->timerId() == sync_db_timer) {
    syncDatabase();
  }
}

void KernelPrivate::threadStarted() {
  billing_server = new binaryrpc::Server;

  connect(billing_server, SIGNAL(newSession(binaryrpc::Session *)),
          this, SLOT(newBillingSession(binaryrpc::Session *)));

  db_manager = new DatabaseManager;

  setState(STARTING);
}

void KernelPrivate::stop() {
  setState(STOPING);

  checkState();
}

void KernelPrivate::initServerExecuted() {
  SqlQuery *q(qobject_cast<SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success) {
    if(q->records.isEmpty()) {
      if(log_level >= smslog::ERROR) {
        LogEncoder::instance()->encode(smslog::ERROR,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_INIT_SERVER_ERR);
      }

      setState(STOPING);
    } else if(state == STARTING) {
      QSqlRecord r(q->records.takeFirst());

      host_ip = r.value(0).toString();
      host_port = r.value(1).toUInt();
      sync_db_time = r.value(2).toInt();
    }
  } else {
    if(log_level >= smslog::WARNING) {
      QHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initServer"));
      params.insert(logparam::ERROR_CODE_P, q->fault_code);
      params.insert(logparam::ERROR_MSG_P, q->fault_string);

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPING);
  }

  delete q;

  checkState();
}

void KernelPrivate::initTariffsExecuted() {
  SqlQuery *q(qobject_cast<SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success) {
    if(state == STARTING) {
      while(!q->records.isEmpty()) {
        QSqlRecord r(q->records.takeFirst());

        Tariff *t(new Tariff(r.value(0).toInt(),
                             r.value(1).toString(),
                             roundUp(r.value(2).toFloat(), 2),
                             NO_UPDATED));

        tariffs.push_back(t);
      }
    }
  } else {
    if(log_level >= smslog::WARNING) {
      QHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initTariffs"));
      params.insert(logparam::ERROR_CODE_P, q->fault_code);
      params.insert(logparam::ERROR_MSG_P, q->fault_string);

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPING);
  }

  delete q;

  checkState();
}

void KernelPrivate::initResourcesExecuted() {
  SqlQuery *q(qobject_cast<SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success) {
    if(state == STARTING) {
      while(!q->records.isEmpty()) {
        QSqlRecord r(q->records.takeFirst());

        Resource *res(new Resource(r.value(0).toInt(),
                                   r.value(1).toInt(),
                                   r.value(2).toString(),
                                   roundUp(r.value(3).toFloat(), 2),
                                   r.value(4).toBool(),
                                   NO_UPDATED));

        resources.push_back(res);
      }
    }
  } else {
    if(log_level >= smslog::WARNING) {
      QHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initResources"));
      params.insert(logparam::ERROR_CODE_P, q->fault_code);
      params.insert(logparam::ERROR_MSG_P, q->fault_string);

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPING);
  }

  delete q;

  checkState();
}

void KernelPrivate::syncDatabaseExecuted() {
  SqlQuery *q(qobject_cast<SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success == false && log_level >= smslog::WARNING) {
    QHash<int, QVariant> params;
    params.insert(logparam::ACTION_P, QString("syncDatabase"));
    params.insert(logparam::ERROR_CODE_P, q->fault_code);
    params.insert(logparam::ERROR_MSG_P, q->fault_string);

    LogEncoder::instance()->encode(smslog::WARNING,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                   params);
  }

  delete q;

  checkState();
}

void KernelPrivate::newBillingSession(binaryrpc::Session *session) {
  connect(session, SIGNAL(onOpen()), this, SLOT(openSession()));
  connect(session, SIGNAL(onClose()), this, SLOT(closeSession()));

  if(state == STARTED) {
    connect(session, SIGNAL(onError(const QString &)),
            this, SLOT(errorSession(const QString &)));
    connect(session, SIGNAL(newMessage(binaryrpc::ServerMessage *)),
            this, SLOT(newMessageSession(binaryrpc::ServerMessage *)));
  } else {
    session->close();
  }
}

void KernelPrivate::openSession() {
  binaryrpc::Session *session = qobject_cast<binaryrpc::Session *>(sender());

  if(!session) {
    return;
  }

  if(log_level >= smslog::INFO) {
    QHash<int, QVariant> params;
    params.insert(logparam::PEER_IP_P, session->peerAddress().toString());
    params.insert(logparam::PEER_PORT_P, session->peerPort());
    params.insert(logparam::LOCAL_IP_P, session->localAddress().toString());
    params.insert(logparam::LOCAL_PORT_P, session->localPort());

    LogEncoder::instance()->encode(smslog::INFO,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_OPEN_SESSION,
                                   params);
  }

  sessions.push_back(session);
}

void KernelPrivate::closeSession() {
  binaryrpc::Session *session(qobject_cast<binaryrpc::Session *>(sender()));

  if(!session) {
    return;
  }

  if(log_level >= smslog::INFO) {
    QHash<int, QVariant> params;
    params.insert(logparam::PEER_IP_P, session->peerAddress().toString());
    params.insert(logparam::PEER_PORT_P, session->peerPort());
    params.insert(logparam::LOCAL_IP_P, session->localAddress().toString());
    params.insert(logparam::LOCAL_PORT_P, session->localPort());

    LogEncoder::instance()->encode(smslog::INFO,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_CLOSE_SESSION,
                                   params);
  }

  sessions.removeAll(session);

  delete session;
}

void KernelPrivate::errorSession(const QString &error_string) {
  binaryrpc::Session *session(qobject_cast<binaryrpc::Session *>(sender()));

  if(!session) {
    return;
  }

  if(log_level >= smslog::WARNING) {
    QHash<int, QVariant> params;
    params.insert(logparam::ERROR_MSG_P, error_string);
    params.insert(logparam::PEER_IP_P, session->peerAddress().toString());
    params.insert(logparam::PEER_PORT_P, session->peerPort());
    params.insert(logparam::LOCAL_IP_P, session->localAddress().toString());
    params.insert(logparam::LOCAL_PORT_P, session->localPort());

    LogEncoder::instance()->encode(smslog::WARNING,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_ERROR_SESSION,
                                   params);
  }

  sessions.removeAll(session);

  delete session;
}

void KernelPrivate::newMessageSession(binaryrpc::ServerMessage *message) {
  binaryrpc::Session *session(qobject_cast<binaryrpc::Session *>(sender()));

  if(!session) {
    return;
  }

  QString method_name(message->name());
  QVariantList parameters(message->parameters());

  if(method_name == "changeResource") {
    bool success(parameters.size() == 2);

    if(!success) {
      message->fault(BRPC_ERR_CODE_NOT_FOUND, BRPC_ERR_CODE_NOT_FOUND_D);
      message->deleteLater();
      return;
    }

    int resource_id(parameters[0].toInt(&success));

    if(!success) {
      message->fault(BRPC_ERR_CODE_NOT_FOUND, BRPC_ERR_CODE_NOT_FOUND_D);
      message->deleteLater();
      return;
    }

    int resource_num(parameters[1].toInt(&success));

    if(!success) {
      message->fault(BRPC_ERR_CODE_NOT_FOUND, BRPC_ERR_CODE_NOT_FOUND_D);
      message->deleteLater();
      return;
    }

    Resource *res = getResource(resource_id);

    if(!res) {
      message->done(QVariant(false));
      message->deleteLater();
      return;
    }

    Tariff *tar = getTariff(res->tariff_id);

    if(!tar) {
      message->done(QVariant(false));
      message->deleteLater();
      return;
    }

    if(res->state == ADDED ||
       res->state == NO_UPDATED ||
       res->state == UPDATED) {
      float new_balance(roundUp(res->balance + resource_num * tar->cost, 2));

      if((new_balance < 0 && res->postpaid) || new_balance >= 0) {
        res->balance = new_balance;

        if(res->state == NO_UPDATED) {
          res->state = UPDATED;
        }

        message->done(QVariant(true));
      } else {
        message->done(QVariant(false));
      }
    } else {
      message->done(QVariant(false));
    }
  } else {
    message->fault(BRPC_ERR_CODE_NOT_FOUND, BRPC_ERR_CODE_NOT_FOUND_D);
  }

  message->deleteLater();
}

void KernelPrivate::syncDatabase() {
  QString insert_tariff;
  QString update_tariff_id;
  QString update_tariff_name;
  QString update_tariff_cost;
  QString delete_tariff;

  for(int i = 0; i < tariffs.size(); i++) {
    Tariff *t = tariffs[i];

    switch(t->state) {
      case NO_UPDATED:
        break;
      case UPDATED:
        if(!update_tariff_id.isEmpty()) {
          update_tariff_id += ",";
        }
        if(!update_tariff_name.isEmpty()) {
          update_tariff_name += " ";
        }
        if(!update_tariff_cost.isEmpty()) {
          update_tariff_cost += " ";
        }

        update_tariff_id += QString::number(t->id);
        update_tariff_name +=
          QString("WHEN %1 THEN '%2'").arg(t->id).arg(t->name);
        update_tariff_cost +=
          QString("WHEN %1 THEN %2").arg(t->id).arg(t->cost);

        t->state = NO_UPDATED;

        break;
      case ADDED:
        if(!insert_tariff.isEmpty()) {
          insert_tariff += ",";
        }
        insert_tariff += QString("(%1,'%2',%3)")
                         .arg(t->id)
                         .arg(t->name)
                         .arg(t->cost);

        t->state = NO_UPDATED;

        break;
      case DELETED:
        if(!delete_tariff.isEmpty()) {
          delete_tariff += ",";
        }
        delete_tariff += QString::number(t->id);

        i--;
        delete t;

        break;
    }
  }

  SqlQuery *query(0);

  if(!insert_tariff.isEmpty() ||
     !update_tariff_name.isEmpty() ||
     !delete_tariff.isEmpty()) {
     query = new SqlQuery(false, true);

     if(!insert_tariff.isEmpty()) {
       query->add(QString("INSERT INTO `billing_tariffs` ("
                          "`id`,"
                          "`name`,"
                          "`cost`) VALUES ").append(insert_tariff));
     }

     if(!update_tariff_id.isEmpty()) {
       query->add(QString(
                    "UPDATE `billing_tariffs` "
                    "SET `name` = CASE `id` %1 END,"
                    "`cost` = CASE `id` %2 END "
                    "WHERE `server_id`=%3 AND `id` IN (%4)")
                  .arg(update_tariff_name)
                  .arg(update_tariff_cost)
                  .arg(server_id)
                  .arg(update_tariff_id));
     }

     if(!delete_tariff.isEmpty()) {
       query->add(QString("DELETE FROM `billing_tariffs` WHERE `id` IN (%1)")
                    .arg(delete_tariff));
     }
  }

  QString insert_resource;
  QString update_resource_id;
  QString update_resource_tariff_id;
  QString update_resource_name;
  QString update_resource_balance;
  QString update_resource_postpaid;
  QString delete_resource;

  for(int i = 0; i < resources.size(); i++) {
    Resource *r = resources[i];

    switch(r->state) {
      case NO_UPDATED:
        break;
      case UPDATED:
        if(!update_resource_id.isEmpty()) {
          update_resource_id += ",";
        }
        if(!update_resource_tariff_id.isEmpty()) {
          update_resource_tariff_id += " ";
        }
        if(!update_resource_name.isEmpty()) {
          update_resource_name += " ";
        }
        if(!update_resource_balance.isEmpty()) {
          update_resource_balance += " ";
        }
        if(!update_resource_postpaid.isEmpty()) {
          update_resource_postpaid += " ";
        }

        update_resource_id += QString::number(r->id);
        update_resource_tariff_id +=
          QString("WHEN %1 THEN %2").arg(r->id).arg(r->tariff_id);
        update_resource_name +=
          QString("WHEN %1 THEN '%2'").arg(r->id).arg(r->name);
        update_resource_balance +=
          QString("WHEN %1 THEN %2").arg(r->id).arg(r->balance);
        update_resource_postpaid +=
          QString("WHEN %1 THEN %2").arg(r->id).arg((int)r->postpaid);

        r->state = NO_UPDATED;

        break;
      case ADDED:
        if(!insert_resource.isEmpty()) {
          insert_resource += ",";
        }
        insert_resource += QString("(%1,%2,'%3',%4,%5)")
                             .arg(r->id)
                             .arg(r->tariff_id)
                             .arg(r->name)
                             .arg(r->balance)
                             .arg((int)r->postpaid);

        r->state = NO_UPDATED;

        break;
      case DELETED:
        if(!delete_resource.isEmpty()) {
          delete_resource += ",";
        }
        delete_resource += QString::number(r->id);

        i--;
        delete r;

        break;
    }
  }

  if(!insert_resource.isEmpty() ||
     !update_resource_id.isEmpty() ||
     !delete_resource.isEmpty()) {
     if(!query) {
       query = new SqlQuery(false, true);
     }

     if(!insert_resource.isEmpty()) {
       query->add(QString("INSERT INTO `billing_resources` ("
                          "`id`,"
                          "`tariff_id`,"
                          "`name`,"
                          "`balance`,"
                          "`postpaid`) VALUES ").append(insert_resource));
     }

     if(!update_resource_id.isEmpty()) {
       query->add(QString(
                    "UPDATE `billing_resources` "
                    "SET `tariff_id` = CASE `id` %1 END,"
                    "`name` = CASE `id` %2 END,"
                    "`balance` = CASE `id` %3 END,"
                    "`postpaid` = CASE `id` %4 END "
                    "WHERE `server_id`=%5 AND `id` IN (%6)")
                  .arg(update_resource_tariff_id)
                  .arg(update_resource_name)
                  .arg(update_resource_balance)
                  .arg(update_resource_postpaid)
                  .arg(server_id)
                  .arg(update_resource_id));
     }

     if(!delete_resource.isEmpty()) {
       query->add(QString("DELETE FROM `billing_resources` WHERE `id` IN (%1)")
                    .arg(delete_resource));
     }
  }

  if(query) {
    connect(query, SIGNAL(executed()), this, SLOT(syncDatabaseExecuted()));

    queries.push_back(query);

    db_manager->execute(query);
  }
}

bool KernelPrivate::readConfig(QSettings *config) {
  config->beginGroup(KERNEL_KEY);

  bool res(config->contains(LOG_LEVEL_KEY));

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(LOG_LEVEL_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(SERVER_ID_KEY);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(SERVER_ID_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_HOST_KEY);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_HOST_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_PORT_KEY);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_PORT_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_NAME_KEY);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_NAME_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_UNAME_KEY);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_UNAME_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_PASS_KEY);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_PASS_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(NUM_CONNECT_DB);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(NUM_CONNECT_DB));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(EXEC_QUERY_TIMEOUT);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(EXEC_QUERY_TIMEOUT));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  //TODO: Check transform parameters
  mutex.lock();
  log_level = (smslog::LogLevel)config->value(LOG_LEVEL_KEY).toInt();
  server_id = config->value(SERVER_ID_KEY).toInt();
  db_host = config->value(DB_HOST_KEY).toString();
  db_port = config->value(DB_PORT_KEY).toInt();
  db_name = config->value(DB_NAME_KEY).toString();
  db_uname = config->value(DB_UNAME_KEY).toString();
  db_pass = config->value(DB_PASS_KEY).toString();
  num_connect_db = config->value(NUM_CONNECT_DB).toInt();
  exec_query_timeout = config->value(EXEC_QUERY_TIMEOUT).toInt();
  mutex.unlock();

  config->endGroup();

  return res;
}

void KernelPrivate::setState(State s) {
  if(state == s)
    return;

  switch(s) {
    case STOPING:
      if(state == STOPED) {
        return;
      } else {
        if(sync_db_timer != -1) {
          killTimer(sync_db_timer);
          sync_db_timer = -1;
        }

        billing_server->close();

        while(!sessions.isEmpty()) {
          binaryrpc::Session *session(sessions.takeFirst());
          session->close();
          session->deleteLater();
        }

        syncDatabase();

        if(log_level >= smslog::INFO) {
          LogEncoder::instance()->encode(smslog::INFO,
                                         logclass::KERNEL,
                                         logclass::KERNEL,
                                         logmsgcodes::K_STOPING_STATE);
        }
      }
      break;
    case STARTING:
      if(state == STARTED) {
        return;
      } else {
        if(log_level >= smslog::INFO) {
          LogEncoder::instance()->encode(smslog::INFO,
                                         logclass::KERNEL,
                                         logclass::KERNEL,
                                         logmsgcodes::K_STARTING_STATE);
        }
        initServer();
        initTariffs();
        initResources();
      }
      break;
    case STOPED:
      if(log_level >= smslog::INFO) {
        LogEncoder::instance()->encode(smslog::INFO,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_STOPED_STATE);
      }
      break;
    case STARTED:
      if(log_level >= smslog::INFO) {
        LogEncoder::instance()->encode(smslog::INFO,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_STARTED_STATE);
      }
      break;
    case ERROR:
      if(log_level >= smslog::INFO) {
        LogEncoder::instance()->encode(smslog::INFO,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_ERROR_STATE);
      }
      break;
  }

  state = s;

  emit Kernel::instance()->stateChanged(state);
}

void KernelPrivate::initServer() {
  SqlQuery *query = new SqlQuery;

  query->add(QString("SELECT h.`ip`,h.`port`,bs.`sync_db_time` "
                     "FROM `billing_servers` bs,`host` h "
                     "WHERE bs.`host_id`=h.`id` AND bs.`id`=%1 LIMIT 1")
             .arg(Kernel::instance()->getServerId()));

  connect(query, SIGNAL(executed()), this, SLOT(initServerExecuted()));

  queries.push_back(query);

  db_manager->execute(query);
}

void KernelPrivate::initTariffs() {
  SqlQuery *query = new SqlQuery;

  query->add("SELECT bt.`id`,bt.`name`,bt.`cost` FROM `billing_tariffs` bt");

  connect(query, SIGNAL(executed()), this, SLOT(initTariffsExecuted()));

  queries.push_back(query);

  db_manager->execute(query);
}

void KernelPrivate::initResources() {
  SqlQuery *query = new SqlQuery;

  query->add(QString("SELECT br.`id`,"
                     "br.`tariff_id`,"
                     "br.`name`,"
                     "br.`balance`,"
                     "br.`postpaid` "
                     "FROM `billing_servers` bs,`billing_resources` br "
                     "WHERE br.`server_id`=bs.`id` AND bs.`id`=%1")
             .arg(Kernel::instance()->getServerId()));

  connect(query, SIGNAL(executed()), this, SLOT(initResourcesExecuted()));

  queries.push_back(query);

  db_manager->execute(query);
}

void KernelPrivate::checkState() {
  switch(state) {
    case STOPING:
      if(queries.isEmpty()) {
        setState(STOPED);
      }
      break;
    case STARTING:
      if(queries.isEmpty()) {
        if(billing_server->listen(QHostAddress(host_ip), host_port)) {
          if(log_level >= smslog::INFO) {
            QHash<int, QVariant> params;
            params.insert(logparam::LOCAL_IP_P, host_ip);
            params.insert(logparam::LOCAL_PORT_P, host_port);

            LogEncoder::instance()->encode(smslog::INFO,
                                           logclass::KERNEL,
                                           logclass::KERNEL,
                                           logmsgcodes::K_LISTEN_SERVER,
                                           params);
          }

          if(sync_db_time >= 5000) {
            sync_db_timer = startTimer(sync_db_time);
          } else {
            sync_db_timer = startTimer(5000);
          }

          setState(STARTED);
        } else {
          if(log_level >= smslog::ERROR) {
            QHash<int, QVariant> params;
            params.insert(logparam::LOCAL_IP_P, host_ip);
            params.insert(logparam::LOCAL_PORT_P, host_port);
            params.insert(logparam::ERROR_CODE_P,
                          (int)billing_server->serverError());
            params.insert(logparam::ERROR_MSG_P, billing_server->errorString());

            LogEncoder::instance()->encode(smslog::ERROR,
                                           logclass::KERNEL,
                                           logclass::KERNEL,
                                           logmsgcodes::K_NOT_LISTEN_SERVER,
                                           params);
          }

          setState(STOPED);
        }
      }
      break;
    default:
      break;
  }
}

KernelPrivate::Tariff *KernelPrivate::getTariff(int id) const {
  Tariff *t(0);
  for(int i = 0; i < tariffs.size(); i++) {
    Tariff *tmp_p(tariffs[i]);
    if(tmp_p->id == id) {
      t = tmp_p;
      break;
    }
  }

  return t;
}

KernelPrivate::Resource *KernelPrivate::getResource(int id) const {
  Resource *r(0);
  for(int i = 0; i < resources.size(); i++) {
    Resource *tmp_p(resources[i]);
    if(tmp_p->id == id) {
      r = tmp_p;
      break;
    }
  }

  return r;
}

float KernelPrivate::roundUp(float val, unsigned int digits) const {
  return qFloor(val * qPow(10.0, digits) + 0.5) / qPow(10.0, digits);
}

Kernel::Kernel() : QObject(0), p(new KernelPrivate) {
  kernel_instance = this;
}

Kernel::~Kernel() {
  if(p) {
    if(p->thread) {
      if(p->thread->isRunning()) {
        p->thread->quit();
        p->thread->wait();
      }
    }
    delete p;
    p = 0;
  }
}

Kernel *Kernel::instance() {
  return kernel_instance;
}

DatabaseManager *Kernel::dbManagerInstance() {
  QMutexLocker l(&p->mutex);
  return p->db_manager;
}

int Kernel::getServerId() const {
  QMutexLocker l(&p->mutex);
  return p->server_id;
}

smslog::LogLevel Kernel::getLogLevel() const {
  return p->log_level;
}

QString Kernel::getDbHost() const {
  QMutexLocker l(&p->mutex);
  return p->db_host;
}

int Kernel::getDbPort() const {
  QMutexLocker l(&p->mutex);
  return p->db_port;
}

QString Kernel::getDbName() const {
  QMutexLocker l(&p->mutex);
  return p->db_name;
}

QString Kernel::getDbUserName() const {
  QMutexLocker l(&p->mutex);
  return p->db_uname;
}

QString Kernel::getDbPassword() const {
  QMutexLocker l(&p->mutex);
  return p->db_pass;
}

int Kernel::getNumConnectDb() const {
  QMutexLocker l(&p->mutex);
  return p->num_connect_db;
}

int Kernel::getExecQueryTimeout() const {
  QMutexLocker l(&p->mutex);
  return p->exec_query_timeout;
}

bool Kernel::readConfig(QSettings *config) {
  return p->readConfig(config);
}

void Kernel::start() {
  p->thread->start();
}

void Kernel::stop() {
  emit p->sigStop();
}

QString Kernel::getLastError() const {
  QMutexLocker l(&p->mutex);
  return p->last_error;
}

} // namespace smsbilling
